﻿//Copyright (c) 2010 Christopher E. S. King (http://chriskingconsulting.com)
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 
// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do 
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Collections;

using King.Text;
using King.Extensions;
using King.Reflection;
using King.Declarative;
using King.Automation;
using System.Diagnostics;

namespace King.TeamFoundation {

    public abstract class TfCommand : TextDeclaration {

        internal static T Activate<T>(params object[] arguments) {
            var result = typeof(T).Activate();

            var dResult = (dynamic)result;
            dResult.Initialize(arguments);

            return (T)result;
        }

        private string m_exe;
        private string m_currentDirectory;
        private string m_commandLine;

        public TfCommand() {
            m_exe = GetType().Name.SplitCamelCase().First().ToLower() + ".exe";
        }

        internal void Initialize(params object[] arguments) {
            var stackTrace = new StackTrace();
            var methodBase =
               (from o in stackTrace.GetFrames()
                let method = o.GetMethod()
                where method.DeclaringType.IsSealed
                select method).First();

            var parametersAndArguments = methodBase.GetParameters().Zip(
                arguments, (p, a) => new { Parameter = p, Argument = a });

            m_currentDirectory = parametersAndArguments
                .Where(o => o.Parameter.Name == "currentDirectory")
                .Select(o => (string)o.Argument)
                .SingleOrDefault();

            if (m_currentDirectory == null)
                m_currentDirectory = Environment.CurrentDirectory;

            m_commandLine = new TextJoin(" ") {
                // command name
                methodBase.DeclaringType.Name.SplitCamelCase().Second().ToLower(),

                // required switch
                methodBase is MethodInfo ? "/" + methodBase.Name : null,

                // arguments
                from o in parametersAndArguments
                    .ExceptWhere(o => o.Parameter.Name == "currentDirectory")
                let parameter = o.Parameter
                let argument = o.Argument
                
                // skip arguments that are false
                where argument != null && !object.Equals(argument, false)
                select new TextJoin(":") {

                    // optional 
                    parameter.IsOptional ? "/" + parameter.Name : null,

                    // value (empty if true)
                    object.Equals(argument, true) ? null : argument
                }
            }.ReadToEnd();
        }
        internal virtual bool OnError(string error) {
            return false;
        }

        protected override IEnumerable Children(DeclarationContext context) {
            return new AutomationProcess(m_exe,
                currentDirectory: m_currentDirectory,
                onError: OnError) {
                m_commandLine
            };
        }
       
        public override void Add(object o) {
            throw new NotSupportedException();
        }
        public override string ToString() {
            return m_commandLine;
        }
    }
    
    public enum TfLock {
        None,
        Checkin,
        Checkout,
    }
    public enum TfFormat {
        Brief,
        Detailed
    }
    public enum TfSort {
        Ascending,
        Descending,
    }
    public enum TfPermission {
        Private,
        Public,
        PublicLimited,
    }
    public enum TfChild {
        Replace,
        Merge,
    }

    public sealed class TfVersionSpec : TextDeclaration {

        protected override IEnumerable Children(DeclarationContext context) {

            if (DateTime != null)
                yield return new TextDeclaration() { "D", DateTime.ToString() };

            if (Changeset != null)
                yield return new TextDeclaration() { "C", DateTime.ToString() };

            if (Label != null)
                yield return new TextDeclaration() { "L", DateTime.ToString() };

            if (Workspace != null)
                yield return new TextDeclaration() { "W", DateTime.ToString() };

            yield return "T";
        }
        public override void Add(object o) {
            throw new NotSupportedException();
        }

        public DateTime? DateTime { get; set; }
        public int? Changeset { get; set; }
        public string Label { get; set; }
        public string Workspace { get; set; }
    }
    public sealed class TfNote : TextDeclaration {

        private object m_name;
        private object m_value;

        public TfNote(object name, object value) {
            m_name = name;
            m_value = value;
        }

        protected override IEnumerable Children(DeclarationContext context) {
            return new TextJoin("=") { 
                new TextDeclaration() { "\"", m_name, "\"" },
                new TextDeclaration() { "\"", m_value, "\"" },
            };
        }
        public override void Add(object o) {
            throw new NotSupportedException();
        }
    }

    public sealed class TfGet : TfCommand {

        public TfGet(string itemspec,
            string version = null,
            bool all = false,
            bool overwrite = false,
            bool force = false,
            bool preview = false,
            bool recursive = false,
            bool remap = false,
            bool noPrompt = false,
            string login = null,
            string currentDirectory = null) {

            Initialize(itemspec, version, all, overwrite, force, preview, recursive, remap, noPrompt, login, currentDirectory);
        }
    }
    public sealed class TfAdd : TfCommand {

        public TfAdd(string itemspec,
            TfLock? @lock = null,
            string type = null,
            bool noPrompt = false,
            bool recursive = false,
            string login = null,
            string currentDirectory = null) {

            Initialize(itemspec, @lock, type, noPrompt, recursive, login, currentDirectory);
        }
    }
    public sealed class TfRename : TfCommand {

        public TfRename(string oldItem,
            string newItem,
            TfLock? @lock = null,
            string login = null,
            string currentDirectory = null) {

            Initialize(oldItem, newItem, @lock, login, currentDirectory);
        }
    }
    public sealed class TfBranch : TfCommand {

        public TfBranch(string oldItem,
            string newItem,
            string version = null,
            bool noGet = false,
            bool noPrompt = false,
            bool silent = false,
            bool checkin = false,
            string comment = null,
            string name = null,
            string login = null,
            string currentDirectory = null) {

            Initialize(oldItem, newItem, version, noGet, noPrompt, silent, checkin, comment, name, login, currentDirectory);
        }
    }
    public sealed class TfMerge : TfCommand {

        public TfMerge(string source,
            string destination,
            bool recursive = false,
            bool force = false,
            bool candidate = false,
            bool discard = false,
            string version = null,
            TfLock? @lock = null,
            bool preview = false,
            bool baseless = false,
            bool noSummary = false,
            bool noImplicitBaseless = false,
            bool conservative = false,
            TfFormat? format = null,
            bool noPrompt = false,
            bool login = false,
            string currentDirectory = null) {

            Initialize(source, destination, recursive, force, candidate, discard, version, @lock, preview, baseless, noSummary,
                noImplicitBaseless, conservative, format, noPrompt, login, currentDirectory);
        }
    }
    public sealed class TfDelete : TfCommand {

        public TfDelete(string itemspec,
            TfLock? @lock = null,
            bool recursive = false,
            string login = null,
            string currentDirectory = null) {

            Initialize(itemspec, @lock, recursive, login, currentDirectory);
        }
    }
    public sealed class TfWorkspace : TfCommand {

        public static TfWorkspace New(
            string workspaceName,
            bool noPrompt = false,
            string template = null,
            string computer = null,
            string comment = null,
            string collection = null,
            TfPermission? permission = null,
            string login = null,
            string currentDirectory = null) {

            return Activate<TfWorkspace>(workspaceName, noPrompt, template, computer, comment, collection, permission, login, currentDirectory);
        }

        public static TfWorkspace Delete(
            string workspaceName,
            string collection = null,
            string login = null,
            string currentDirectory = null) {

            return Activate<TfWorkspace>(workspaceName, collection, login, currentDirectory);
        }
        
        internal TfWorkspace() { }

        public TfWorkspace(
            string workspaceName,
            string computer = null,
            string comment = null,
            string collection = null,
            string newName = null,
            TfPermission? permission = null,
            string login = null,
            string currentDirectory = null) {

            Initialize(workspaceName, computer, comment, collection, newName, permission, login, currentDirectory);
        }
    }
    public sealed class TfWorkspaces : TfCommand {

        public TfWorkspaces(
            string workspaceName,
            string owner = null,
            string computer = null,
            string collection = null,
            string format = null,
            string updateUserName = null,
            string updateComputerName = null,
            string login = null,
            string currentDirectory = null) {

            Initialize(workspaceName, owner, computer, collection, format, updateUserName, updateComputerName, login, currentDirectory);
        }

        public TfWorkspaces(
            string remove = null,
            string collection = null,
            string login = null,
            string currentDirectory = null) {

            Initialize(remove, collection, login, currentDirectory);
        }
    }
    public sealed class TfWorkfold : TfCommand {

        public static TfWorkfold Map(
            string serverFolder,
            string localFolder,
            string collection = null,
            string workspace = null,
            string login = null,
            string currentDirectory = null) {

            return Activate<TfWorkfold>(serverFolder, localFolder, collection, workspace, login, currentDirectory);
        }

        public static TfWorkfold Unmap(
            string serverOrLocalFolder,
            string collection = null,
            string workspace = null,
            string login = null,
            string currentDirectory = null) {

            return Activate<TfWorkfold>(serverOrLocalFolder, collection, workspace, login, currentDirectory);
        }

        public static TfWorkfold Cloak(
            string serverFolder,
            string workspace = null,
            string collection = null,
            string login = null,
            string currentDirectory = null) {

            return Activate<TfWorkfold>(serverFolder, workspace, collection, login, currentDirectory);
        }

        public static TfWorkfold Decloak(
            string serverFolder,
            string workspace = null,
            string collection = null,
            string login = null,
            string currentDirectory = null) {

            return Activate<TfWorkfold>(serverFolder, workspace, collection, login, currentDirectory);
        }

        internal TfWorkfold() {
            Initialize();
        }

        public TfWorkfold(
            string localFolder,
            string login = null,
            string currentDirectory = null) {

            Initialize(localFolder, login, currentDirectory);
        }

        public TfWorkfold(
            string serverFolder,
            string workspace = null,
            string collection = null,
            string login = null,
            string currentDirectory = null) {

            Initialize(serverFolder, workspace, collection, login, currentDirectory);
        }
    }
    public sealed class TfCheckout : TfCommand {

        public TfCheckout(string itemspec,
            TfLock? @lock = null,
            bool recursive = false,
            string type = null,
            string login = null,
            string currentDirectory = null) {

            Initialize(itemspec, @lock, recursive, login, currentDirectory);
        }

        internal override bool OnError(string error) {

            var lines = error.StringSplit(Environment.NewLine).ToQueue();
            string line = lines.Dequeue();

            //$/king/private/user/cking/lab2/dev/src/SRSolutions/AppNetwork/Network/Network.csproj:
            //   opened for edit in A1046;kcholera
            while (Regex.IsMatch(line, "^[$].*:$")) {
                while (line.Any() && Regex.IsMatch(line = lines.Dequeue(), "   opened for edit in")) {
                    if (lines.None())
                        return true;
                }
            }

            return false;
        }
    }
    public sealed class TfCheckin : TfCommand {

        public TfCheckin(
            string shelveset = null,
            string author = null,
            bool noPrompt = false,
            string notes = null,
            bool bypass = false,
            bool force = false,
            string collection = null,
            string login = null,
            string currentDirectory = null) {

            Initialize(shelveset, author, noPrompt, notes, bypass, force, collection, login, currentDirectory);
        }

        public TfCheckin(
            string itemspec,
            string author = null,
            string comment = null,
            bool noPrompt = false,
            string notes = null,
            string @override = null,
            bool recursive = false,
            bool saved = false,
            bool validate = false,
            bool bypass = false,
            bool force = false,
            string login = null,
            string currentDirectory = null) {

            Initialize(itemspec, author, comment, noPrompt, notes, @override, recursive, saved,
                validate, bypass, force, login, currentDirectory);
        }
    }
    public sealed class TfHistory : TfCommand {

        public TfHistory(string itemspec,
            string version = null,
            int? stopAfter = null,
            bool recursive = false,
            string userName = null,
            TfFormat? format = null,
            bool slotMode = false,
            bool itemMode = false,
            bool noPrompt = false,
            TfSort? sort = null,
            string collection = null,
            string login = null,
            string currentDirectory = null) {

            Initialize(itemspec, version, stopAfter, recursive, userName, format, slotMode, itemMode, noPrompt, sort, collection, login, currentDirectory);
        }
    }
    public sealed class TfLabel : TfCommand {

        public static TfLabel Delete(
            string labelName,
            string itemspec,
            string collection = null,
            string login = null,
            string currentDirectory = null) {

            return Activate<TfLabel>(labelName, itemspec, collection, login, currentDirectory);
        }

        public TfLabel(string labelName,
            string itemspec,
            string version = null,
            string owner = null,
            string comment = null,
            TfChild? child = null,
            bool recursive = false,
            string login = null,
            string collection = null,
            string currentDirectory = null) {

            Initialize(labelName, itemspec, version, owner, comment, child, recursive, login, collection, currentDirectory);
        }
    }
    public sealed class TfLabels : TfCommand {

        public TfLabels(
            string labelName,
            string owner = null,
            TfFormat? format = null,
            string login = null,
            string collection = null,
            string currentDirectory = null) {

            Initialize(labelName, owner, format, login, collection, currentDirectory);
        }
    }
    public sealed class TfRollback : TfCommand {

        public TfRollback(
            string itemspec,
            string changeset = null,
            bool recursive = false,
            bool keepMergeHistory = false,
            TfLabel @lock = null,
            string version = null,
            bool noPrompt = false,
            string login = null,
            string currentDirectory = null) {

            Initialize(itemspec, changeset, recursive, keepMergeHistory, @lock, version, noPrompt, login, currentDirectory);
        }

        public TfRollback(
            string itemspec,
            string toVersion = null,
            bool recursive = false,
            TfLabel @lock = null,
            string version = null,
            bool keepMergeHistory = false,
            bool noPrompt = false,
            string login = null,
            string currentDirectory = null) {

            Initialize(itemspec, toVersion, recursive, @lock, version, keepMergeHistory, noPrompt, login, currentDirectory);
        }
    }
    public sealed class TfDir : TfCommand {

        public TfDir(string itemspec,
            string version = null,
            bool recursive = false,
            bool folders = false,
            bool deleted = false,
            string collection = null,
            string login = null,
            string currentDirectory = null) {

            Initialize(itemspec, version, recursive, folders, deleted, collection, login, currentDirectory);
        }
    }
    public sealed class TfStatus : TfCommand {

        public TfStatus(string itemspec,
            bool recursive = false,
            string user = null,
            string workspace = null,
            TfFormat? format = null,
            string collection = null,
            string login = null,
            string currentDirectory = null) {

            Initialize(itemspec, recursive, user, workspace, format, collection, login, currentDirectory);
        }
    }


    [CommandGroup]
    public sealed class TfptScorch : TfCommand {

        public TfptScorch(
            string itemspec,
            bool recursive = false,
            string exclude = null,
            int? batchSize = null,
            bool noPrompt = false,
            bool deletes = false,
            bool diff = false,
            string currentDirectory = null) {

            Initialize(itemspec, recursive, exclude, batchSize, noPrompt, deletes, diff, currentDirectory);
        }

        internal override bool OnError(string error) {
            return error.Contains("No inconsistencies found.");
        }
    }


    public sealed class BisSubscribeOptionAttribute : Attribute {
    }
    public enum DeliveryType {
        EmailHtml,
        EmailPlaintext,
        Soap
    }
    public sealed class BisSubscribe : TextDeclaration {

        public static string BisSubscribeExe = "bisSubscribe.exe";

        protected override IEnumerable Substance() {
            return Substantial;
        }
        protected override IEnumerable Children(DeclarationContext context) {
            //var process = new TProcess(BisSubscribeExe) {
            //    Arguments = new TextDeclaration(" ") {
            //        Unsubscribe ? "/unsubscribe" : null,

            //        // switches
            //        from o in GetType().GetInstancePublic<PropertyInfo>()
            //        where o.IsDefined<BisSubscribeOptionAttribute>()
            //        select GetOptionValue(o),
            //    }
            //};

            //process.OnError += OnError;

            //return new[] { process };
            throw new NotImplementedException();
        }
        public override void Add(object o) {
            throw new NotSupportedException();
        }

        private object GetOptionValue(PropertyInfo propertyInfo) {
            var value = propertyInfo.GetValue(this);
            if (value == null)
                return null;

            var name = "/" + propertyInfo.Name.ToLower();

            var result = new TextJoin(" ") { 
                name, 
                new TCommandLineArgument() {
                    value.ToString() 
                }
            }.ReadToEnd();

            return result;
        }

        public event Func<string, bool> OnError;

        [BisSubscribeOption]
        public string EventType { get; set; }

        [BisSubscribeOption]
        public string Filter { get; set; }

        [BisSubscribeOption]
        public string Address { get; set; }

        [BisSubscribeOption]
        public string Server { get; set; }

        [BisSubscribeOption]
        public string Collection { get; set; }

        [BisSubscribeOption]
        public DeliveryType DeliveryType { get; set; }

        [BisSubscribeOption]
        public int? Id { get; set; }

        public bool Unsubscribe { get; set; }
    }
}